/*
 * CShader.cpp
 *
 *      Author: grifos
 */

#include "CShader.h"
#include <stdio.h>
#include <cstring>
#include <iostream>
#include <sstream>
#include <fstream>
#include <string>

CShader::CShader() {
	_CompiledShader = 0;
	_loaded = false;
//	_currentShader = NULL;
}

CShader::~CShader() {
}

GLhandleARB CShader::loadShader(const std::string& shaderPath)
{
	  bool vertexshader = false;
	  bool fragmentshader = false;

	  // On regarde si il s'agit d'un vertex ou fragment shader
	  if(shaderPath.find(".vert") != std::string::npos){
	    vertexshader=true;
	  }else{
	    if(shaderPath.find(".frag") != std::string::npos){
	      fragmentshader=true;
	    }
	  }

	  // On ouvre le fichier pour la lecture
	  std::ifstream file(shaderPath.c_str());
	  if(!file)
	  {
	    return 0;
	  }

	  // On le lis
	  std::stringstream buffer;
	  buffer << file.rdbuf();
	  file.close();

	  std::string s = buffer.str();

	  std::cout << "Le contenu du fichier source: " << std::endl;
	  std::cout << s << std::endl;
	  // On crée la place pour le fichier source GLSL
	  GLcharARB * source = new GLcharARB[4*(buffer.str().size()/4+1)];
	  if(source == 0)
	    return 0;

	  unsigned int i;
	  for(i =0; i < buffer.str().size(); ++i){
	   source[i] = s[i];
	  }
	  source[i] = '\0';

	  // On crée le fichier source du shader
	  GLenum so;
	  // On crée l'objet source du shader où va etre mis la source
	  if(vertexshader)
	  {
	    so = glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB);
	  }
	  else if(fragmentshader)
	  {
	    so = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB);
	  }

	  glShaderSourceARB(so, 1,(const GLcharARB**) &source, 0);

	  return so;
}

/**
 * Méthode qui link les shader
 */
GLhandleARB CShader::linkShaders(GLhandleARB *object, const unsigned int & nb)
{
	if(object==0)
	    return 0;

	// On crée le programme de destination
	  GLhandleARB po = 0;
	  po = glCreateProgramObjectARB();

	  // On attache les objet compilé au programme
	  for(unsigned int i = 0; i < nb; ++i){
	    if(object[i]>0)
	      glAttachObjectARB(po,object[i]);
	  }

	  // On link le programme contenant les shaders (vertex + fragment)
	  glLinkProgramARB(po);

	  // On regarde si le linkage c'est fait correctement
	  int status;
	  glGetObjectParameterivARB(po, GL_OBJECT_LINK_STATUS_ARB, &status);
	  if(status==0){
	    int length = 0;
	    glGetObjectParameterivARB(po, GL_OBJECT_INFO_LOG_LENGTH_ARB, &length);

	    return 0;
	  }
	  // On retourne le programme linké
	  return po;
}

/**
 * On compile le shader
 */
bool CShader::compileShader(GLhandleARB object)
{
	 if(object==0)
	 {
		 std::cout << "Objet vide" << std::endl;
	    return false;
	 }

	 // On compile
	  glCompileShaderARB(object);

	  int status;
	  // On regarde si la compilation c'est bien effectué
	  glGetObjectParameterivARB(object, GL_OBJECT_COMPILE_STATUS_ARB, &status);
	  if(status==0){
	    int length = 0;
	    glGetObjectParameterivARB(object, GL_OBJECT_INFO_LOG_LENGTH_ARB, &length);
	    GLsizei minlength = std::min(4096,length);
	    char logstring[4096];
		glGetInfoLogARB(object, minlength, 0, logstring);
		std::cerr << logstring << std::endl;
	    return false;
	  }
	  return true;
}

bool CShader::Load(char *name)
{
	_ShaderName = name;
	const std::string n(name);
	GLhandleARB so[3];
	memset(so, 0, sizeof(GLhandleARB)*3);

	// On récupere les fichiers où sont écris les shaders
	std::string s1 = "./shader/"+n+".vert";
	// Chargement du fichier du vertex shader
	std::cout << "Chargement du fichier du vertex shader" << std::endl;
	so[0] = loadShader(s1);
	if(so[0]==0){
		std::cout << "loading shader "+s1+" failed (exiting...)" << std::endl;
		return false;
	}
	// On compile le vertex shader
	if(!compileShader(so[0])){
		std::cout << "compiling shader "+s1+" failed (exiting...)" << std::endl;
		return false;
	}

	std::string s2 = "./shader/"+n+".frag";
	// Chargement du fichier du fragment shader
	std::cout << "Chargement du fichier du fragment shader" << std::endl;
	so[1] = loadShader(s2);
	if(so[1]==0){
		std::cout << "loading shader "+s2+" failed (exiting...)" << std::endl;
		return false;
	}
	// On compile le fragment shader
	if(!compileShader(so[1])){
		std::cout << "compiling shader "+s2+" failed (exiting...)" << std::endl;
		return false;
	}


	// On link les shaders, on récupère le miniprogramme crée et on le stock dans notre objet _CompiledShader
	_CompiledShader = linkShaders(so,2);

	// On clean les objet compilé
	glDeleteObjectARB(so[0]);
	glDeleteObjectARB(so[1]);

	_loaded = true;
	return true;
}



void CShader::StartUse()
{
//	_currentShader = this;
	glUseProgramObjectARB(_CompiledShader);
}



void CShader::EndUse()
{
//	_currentShader = NULL;
	glUseProgramObjectARB(0);
}

/**
 * Envoit un id de texture au shader en fonction du nom de la varaible
 */
void CShader::UniformTexture(const std::string & ext, GLint slot)
{
	glActiveTexture(GL_TEXTURE0+slot);
	int id = glGetUniformLocation(_CompiledShader, ext.c_str());
	glUniform1iARB(id, slot);
}

/**
 * Envoit un GLint au shader en fonction du nom de la variable
 */
void CShader::Uniform(const std::string & ext, GLint value)
{
	int id = glGetUniformLocation(_CompiledShader, ext.c_str());
	glUniform1iARB(id, value);
}

/**
 * Envoit un GLfloat au shader en fonction du nom de la variable
 */
void CShader::Uniform(const std::string & ext, GLfloat value)
{
	int id = glGetUniformLocation(_CompiledShader, ext.c_str());
	glUniform1fARB(id, value);
}

